import isObject from 'lodash/isObject'
import isEmpty from 'lodash/isEmpty'
import isString from 'lodash/isString'
import isFunction from 'lodash/isString'
import merge from 'lodash/merge'
import IntlMessageFormat from 'intl-messageformat'
import spanner from '../core'
import * as WebUtils from '../core/utils/WebUtils'
import { formatLocale, compatLocale, determineLocale } from './helper'

const DEF_LOCALE = 'zh-CN'
const DEF_SEARCH = ['__lang', 'lang', 'local']

interface IFile {
  /**
   *  语言文件, 例如 本地{"en-US": require("path/src/i18n/en-US.js")}
   *  注意必须采用require，将资源文件保存下来，因为浏览器端不支持require语法，路径也发生了变动
   */
  [PropName: string]: object;
}

/**
   *  远程语言文件的json(js)地址，{"en-US": "http://xxxx/en-US.json"}
   */
interface IPath {
  [PropName: string]: string;
}

/**
 * 资源请求处理函数，一般用于用户请求翻译平台url，返回语言文件的json地址或空串(表示找不到)
 */
interface IFunc {
  (lang: string, code: string): string;
}

// i18n的初始化方法入参
interface IParams {
  lang: string;
  local: IFile;
  remote?: IPath | IFunc;
  defaultLocale: string;
}

interface IOptions {
  local: IFile;
  remote: IPath | IFunc;
  localData: {}; // 本地翻译资源，请求local得到
  remoteData: {}; // 远程翻译资源，请求remote得到
  requireLocale: string; // 用户请求需要的语言，正常不传，由系统自动检测
  currentLocale: string; // 当前实际使用的语言，正常不传，由系统自动判断
  defaultLocale: string; // currentLocale无法正常找到时，默认使用的语言
  requestLocaleName: string[]; // 从URL、Cookie中获取语言的参数名称兼容
}

export default class I18n {

  private i18nConfig: IOptions = {
    local: {},
    remote: {},
    localData: {},
    remoteData: {},
    requireLocale: '',
    currentLocale: '',
    defaultLocale: DEF_LOCALE,
    requestLocaleName: DEF_SEARCH,
  }

  constructor(initParams: IParams) {
    if (initParams) {
      this.init(initParams)
    }
  }

  /**
   * 初始化i18n
   */
  public init(initParams: IParams): void {
    this.i18nConfig = {
      local: {},
      remote: {},
      localData: {},
      remoteData: {},
      requireLocale: '',
      currentLocale: '',
      defaultLocale: DEF_LOCALE,
      requestLocaleName: DEF_SEARCH,
    }
    // 初始化配置
    this.i18nConfig.local = merge({}, this.i18nConfig.local, initParams.local)
    if (isFunction(initParams.remote) || WebUtils.isPromise(initParams.remote)) {
      this.i18nConfig.remote = initParams.remote!
    } else if (isObject(initParams.remote)) {
      this.i18nConfig.remote = merge({}, this.i18nConfig.remote, initParams.remote)
    }
    if (!isEmpty(initParams.defaultLocale) && isString(initParams.defaultLocale!)) {
      this.i18nConfig.defaultLocale = formatLocale(initParams.defaultLocale)
    }
    if (isString(initParams.lang)) {
      this.i18nConfig.requireLocale = initParams.lang
    }
    if (isEmpty(this.i18nConfig.requireLocale)) {
      this.i18nConfig.requireLocale = determineLocale(this.i18nConfig.requestLocaleName, this.i18nConfig.defaultLocale)
    }
    // 初始化默认语言包
    const defaultLangPack = this.i18nConfig.local[this.i18nConfig.defaultLocale]
    if (defaultLangPack !== null) {
      this.i18nConfig.localData[this.i18nConfig.defaultLocale] = defaultLangPack
    }
    this.handleLocalLocales(this.i18nConfig.requireLocale!)
  }

  /**
   * 获得翻译数据
   */
  public get(key: string, jsn: any, def: string): string {
    // 参数兼容处理
    if (isString(jsn) && def === undefined) {
      def = jsn
      jsn = null
    }
    // 语言文本提取
    let extractLangPack: object
    if (isString(this.i18nConfig.currentLocale)) {
      // 优先远程语言包
      extractLangPack = this.i18nConfig.remoteData[this.i18nConfig.currentLocale!]
      if (extractLangPack != null) {
        let msg: string = this.compatDescend(extractLangPack, key)
        if (WebUtils.isDefined(msg)) {
          if ((msg += '').length > 0) {
            return this.formatMessage(msg, jsn)
          }
          console.warn(`I18n key '${key}' value is empty in the remote language pack: ${this.i18nConfig.currentLocale}`)
        } else {
          console.warn(`I18n key '${key}' is not defined in the remote language pack: ${this.i18nConfig.currentLocale}`)
        }
      }
      // 降级本地语言包
      extractLangPack = this.i18nConfig.localData[this.i18nConfig.currentLocale!]
      if (extractLangPack != null) {
        let msg: string = this.compatDescend(extractLangPack, key)
        if (WebUtils.isDefined(msg)) {
          if ((msg += '').length > 0) {
            return this.formatMessage(msg, jsn)
          }
          console.warn(`I18n key '${key}' value is empty in the local language pack: ${this.i18nConfig.currentLocale}`)
        } else {
          console.warn(`I18n key '${key}' is not defined in the local language pack: ${this.i18nConfig.currentLocale}`)
        }
      }
    }
    // 降级默认语言包
    if (this.i18nConfig.defaultLocale !== this.i18nConfig.currentLocale) {
      extractLangPack = this.i18nConfig.localData[this.i18nConfig.defaultLocale]
      if (extractLangPack != null) {
        let msg: string = this.compatDescend(extractLangPack, key)
        if (WebUtils.isDefined(msg)) {
          if ((msg += '').length > 0) {
            return this.formatMessage(msg, jsn)
          }
          console.warn(`I18n key '${key}' value is empty in the default language pack: ${this.i18nConfig.defaultLocale}`)
        } else {
          console.warn(`I18n key '${key}' is not defined in the default language pack: ${this.i18nConfig.defaultLocale}`)
        }
      }
    }
    // 降级默认值与键
    return WebUtils.isDefined(def) ? this.formatMessage(def + '', jsn) : key;
  }

  /**
   * 获得语言信息
   */
  public getLocale(): object {
    return {
      require: this.i18nConfig.requireLocale,
      current: this.i18nConfig.currentLocale,
      default: this.i18nConfig.defaultLocale,
    }
  }

  /**
   * 设置用户语言
   */
  public setLocale(lang: string): void {
    if (!isEmpty(lang) && isString(lang)) {
      this.handleLocalLocales(this.i18nConfig.requireLocale = lang)
    }
  }

  /**
   * 请求远程资源
   */
  public async syncLocales(): Promise<void> {
    return this.handleRemoteLocales(this.i18nConfig.requireLocale!)
  }

  /**
   * 加载本地语言资源
   */
  private handleLocalLocales(requestLocale: string): void {
    if (!isEmpty(requestLocale)) {
      let localLangPack: object = this.i18nConfig.localData[requestLocale]
      if (localLangPack == null) {
        localLangPack = this.i18nConfig.local[requestLocale]
        if (localLangPack == null) {
          // 兼容语言处理
          const adaptLocale: string = compatLocale(requestLocale)
          if (requestLocale !== adaptLocale) {
            localLangPack = this.i18nConfig.local[adaptLocale]
          }
        }
      }
      if (localLangPack != null) {
        this.i18nConfig.currentLocale = requestLocale
        if (this.i18nConfig.defaultLocale !== requestLocale) {
          this.i18nConfig.localData[this.i18nConfig.currentLocale] = localLangPack
        }
        console.log(`I18n local init ok: ${this.i18nConfig.currentLocale}`, this.getLocale())
        return
      }
    }
    console.warn(`Failed to process local language pack: ${requestLocale}`, this.getLocale())
  }

  /**
   * 加载远程语言资源
   */
  private async handleRemoteLocales(requestLocale: string): Promise<void> {
    let remoteLangPack: string
    if (isFunction(this.i18nConfig.remote) || WebUtils.isPromise(this.i18nConfig.remote)) {
      try {
        remoteLangPack = await (<IFunc>this.i18nConfig.remote)(requestLocale, compatLocale(requestLocale))
      } catch (e) {
        console.error(e)
      }
    } else {
      if (isObject(this.i18nConfig.remote)) {
        remoteLangPack = this.i18nConfig.remote![requestLocale]
        if (isEmpty(remoteLangPack)) {
          // 兼容语言处理
          const adaptLocale = compatLocale(requestLocale)
          if (requestLocale !== adaptLocale) {
            remoteLangPack = this.i18nConfig.remote![adaptLocale]
          }
        }
      }
    }
    // 请求远程语言资源
    const self = this
    return new Promise((resolve) => {
      if (isEmpty(remoteLangPack)) {
        resolve()
      } else {
        spanner.request.get(remoteLangPack, { unthrow: true }).then((packet: any) => {
          if (isEmpty(packet)) {
            console.warn(`Failed to process remote language pack: ${remoteLangPack}`, packet)
          } else {
            self.i18nConfig.remoteData[self.i18nConfig.currentLocale = requestLocale] = packet
            console.log(`I18n remote sync ok: ${self.i18nConfig.currentLocale}`, this.getLocale())
          }
          resolve()
        }).catch((e) => {
          console.error(`Failed to request remote language pack: ${remoteLangPack}`, e)
          resolve()
        })
      }
    })
  }

  private compatDescend(pkg: object, key: string): string {
    return pkg.hasOwnProperty(key) ? pkg[key] : key.split('.').reduce(function (a, b) {
      return (a || 0)[b]
    }, pkg)
  }

  private formatMessage(msg: string, jsn: any): string {
    if (isObject(jsn)) {
      const loc = this.i18nConfig.currentLocale || this.i18nConfig.defaultLocale
      try {
        msg = new IntlMessageFormat(msg, loc).format(jsn)
      } catch (e) {
        console.error(`Failed to format i18n text: ${msg}`, e)
      }
    }
    return msg
  }

}
